package XMLParser;

import java.awt.Color;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import Cards.Card;
import Cards.Card.CardType;
import Cards.Money_card;
import Cards.Move_card;
import Cards.Move_card.eGoTo;
import Monopol.AssetGroups.AllServices;
import Monopol.AssetGroups.AllTransportations;
import Monopol.AssetGroups.State;
import Square.City;
import Square.Service;
import Square.Square;
import Square.Square.fixedSquares;
import Square.Transportation;
import Utils.Utils;
import XMLParser.monopoly.jaxb.CardBase;
import XMLParser.monopoly.jaxb.CityType;
import XMLParser.monopoly.jaxb.GetOutOfJailCard;
import XMLParser.monopoly.jaxb.GotoCard;
import XMLParser.monopoly.jaxb.GotoJailSquareType;
import XMLParser.monopoly.jaxb.JailSlashFreeSpaceSquareType;
import XMLParser.monopoly.jaxb.MonetaryCard;
import XMLParser.monopoly.jaxb.Monopoly;
import XMLParser.monopoly.jaxb.ParkingSquareType;
import XMLParser.monopoly.jaxb.SimpleAssetType;
import XMLParser.monopoly.jaxb.SquareBase;
import XMLParser.monopoly.jaxb.SquareType;
import XMLParser.monopoly.jaxb.StartSquareType;
import XMLParser.monopoly.jaxb.Monopoly.Board;
import XMLParser.monopoly.jaxb.Monopoly.Surprise;
import XMLParser.monopoly.jaxb.Monopoly.Warrant;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;

/**
 * 
 */
/**
 * @author Avia Tam
 * @author Assaf Miron
 *
 */
public class MonopolyXML {

    public enum JAXBCardTypes {

	MONETARY_CARD, GOTO_CARD, GETOUT_CARD
    }
    private State[] states;
    private AllTransportations allTransportaions;
    private AllServices allServices;
    private ArrayList<Card> surpriseDeck;
    private ArrayList<Card> warningDeck;
    private Square[] squaresOrder;

    ;
    private Boolean parseOK = true;

    /**
     *
     */
    public MonopolyXML(URL configFile) {
        super();
        JAXBContext jaxbContext;
        try {
            // Create a New Instance of the Monopoly JAXB Package
            jaxbContext = JAXBContext.newInstance(Monopoly.class);
            parseXML(jaxbContext, configFile.openStream());
        } catch (JAXBException e) {
            parseOK = false;
            // Print out the Error Stack Trace
            e.printStackTrace();
        } catch (IOException ex) {
            parseOK = false;
            // Print out the Error Stack Trace
            ex.printStackTrace();
        }

    }

    public MonopolyXML(String xml) {
        super();
        JAXBContext jaxbContext;
        try {
            // Create a New Instance of the Monopoly JAXB Package
            jaxbContext = JAXBContext.newInstance("XMLParser.monopoly.jaxb");
            InputStream is;
            try {
                is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
                parseXML(jaxbContext, is);
            } catch (UnsupportedEncodingException ex) {
                Logger.getLogger(MonopolyXML.class.getName()).log(Level.SEVERE, null, ex);
            }

        } catch (JAXBException e) {
            parseOK = false;
            // Print out the Error Stack Trace
            e.printStackTrace();
        }
    }

    private void parseXML(JAXBContext jaxbContext, InputStream configFile) {
        try {
            // Create an Unmarsheller for the JAXB Context
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            // Unmarshall the XML Object from the Configuration File
            Monopoly jaxbMonopoly = (Monopoly) unmarshaller.unmarshal(configFile);
            getAssets(jaxbMonopoly.getAssets());
            surpriseDeck = getSurpriseCards(jaxbMonopoly.getSurprise());
            warningDeck = getWarningCards(jaxbMonopoly.getWarrant());
            squaresOrder = setBoardOrder(jaxbMonopoly.getBoard());
        } catch (JAXBException e) {
            parseOK = false;
            // Print out the Error Stack Trace
            e.printStackTrace();
        }
    }

    /**
     * @return the states
     */
    public State[] getStates() {
        return states;
    }

    /**
     * @return the allTransportaions
     */
    public AllTransportations getAllTransportaions() {
        return allTransportaions;
    }

    /**
     * @return the allServices
     */
    public AllServices getAllServices() {
        return allServices;
    }

    /**
     * @return the surpriseDeck
     */
    public ArrayList<Card> getSurpriseDeck() {
        return surpriseDeck;
    }

    /**
     * @return the warningDeck
     */
    public ArrayList<Card> getWarningDeck() {
        return warningDeck;
    }

    /**
     * @return the squaresOrder
     */
    public Square[] getSquaresOrder() {
        return squaresOrder;
    }

    /**
     * Create all the Monopoly Game Assets Groups from a Configuration JAXB Object
     * @param assetsConfig The Assets JAXB Configuration Object
     */
    public void getAssets(Monopoly.Assets assetsConfig) {
        states = getStates(assetsConfig.getCountries());
        allTransportaions = getTransportations(assetsConfig.getTransportations());
        allServices = getServices(assetsConfig.getUtilities());
    }

    /**
     * Creates the States For the Monopoly Game from a Configuration JAXB Object
     * @param countriesConfig The Countries JAXB Configuration Object
     * @return An Array of {@link State}
     */
    public State[] getStates(Monopoly.Assets.Countries countriesConfig) {
        // Create a Color Array for the States Colors
        Color[] stateColors = {Color.pink, Color.blue, Color.yellow, Color.black, Color.green, Color.red, Color.magenta, Color.cyan, Color.orange, Color.getColor("Purple"), Color.darkGray};
        // Get the Countries from the Configuration
        List<Monopoly.Assets.Countries.Country> countries = countriesConfig.getCountry();
        // Create a State Array the Size of the Countries from the Configuration File
        State[] states = new State[countries.size()];
        // Create an Index Counter
        Integer i = 0;
        // Create a State Object From the Countries in the File
        for (Monopoly.Assets.Countries.Country oCountry : countries) {
            // Create the State
            states[i] = new State(oCountry.getName(), stateColors[i]);
            // Create the Cities to the State
            for (CityType oCity : oCountry.getCity()) {
                // Add the Cities to the State
                states[i].addCity(getCity(oCity, states[i]));
            }
            // Increment the Index Counter
            i++;
        }
        // Return the States
        return states;
    }

    /**
     * Creates the Transportations For the Monopoly Game from a Configuration JAXB Object
     * @param transpConfig The Transportation JAXB Configuration Object
     * @return An All Transportation Monopoly Object as {@link AllTransportations}
     */
    public AllTransportations getTransportations(Monopoly.Assets.Transportations transpConfig) {
        AllTransportations retAllTransportations = new AllTransportations();
        // Get the Transportations from the Configuration
        List<SimpleAssetType> transportations = transpConfig.getTransportation();
        // Get the Cost if Player has All the Transportations
        Double dAllTransCost = ((Long) transpConfig.getStayCost()).doubleValue();
        // Create the Transportation Objects
        for (SimpleAssetType oTransportation : transportations) {
            // Create and Add a New Transportation
            retAllTransportations.createTransportation(
                    oTransportation.getName(), // Set the Transportation Name
                    // Set the Transportation Stay Cost as a Double Array
                    Utils.createDoubleArray(
                    // If Only part of the Transportation belong to a Player
                    ((Long) oTransportation.getStayCost()).doubleValue(),
                    // If All of the Transportations Belong to a Single Player
                    dAllTransCost),
                    // Set the Transportation Cost
                    ((Long) oTransportation.getCost()).doubleValue());
        }
        // Return All of the Transportations
        return retAllTransportations;
    }

    /**
     * Creates the Services For the Monopoly Game from a Configuration JAXB Object
     * @param utilConfig The Utilities JAXB Configuration object
     * @return An All Services Monopoly Object as {@link AllServices}
     */
    public AllServices getServices(Monopoly.Assets.Utilities utilConfig) {
        AllServices retAllServices = new AllServices();
        // Get the Utilities from the Configuration
        List<SimpleAssetType> utilities = utilConfig.getUtility();
        // Get the Cost if the Player has All the Utilities
        Double dAllUtils = ((Long) utilConfig.getStayCost()).doubleValue();
        // Create the Services Objects
        for (SimpleAssetType oUtility : utilities) {
            // Create and Add a New Service
            retAllServices.createService(
                    oUtility.getName(), // Set the Service Name
                    // Set the Services Stay Cost as a Double Array
                    Utils.createDoubleArray(
                    // If only Part of the Services Belong to a Player
                    ((Long) oUtility.getStayCost()).doubleValue(),
                    // If All of the Services belong to a Single Player
                    dAllUtils),
                    // Set the Service Cost
                    ((Long) oUtility.getCost()).doubleValue());
        }
        // Return All of the Services
        return retAllServices;
    }

    /**
     * Create a City of an JAXB City Type
     * @param oCity The JAXB City Type
     * @param oState The State Object the City belongs to
     * @return A Monopoly {@link City}
     */
    private City getCity(CityType oCity, State oState) {
        // Return the New City Created
        return new City(oCity.getName(), // Set the Name of the City
                // Set the Stay Cost as a Double Array
                Utils.createDoubleArray(((Long) oCity.getStayCost()).doubleValue(), // With No Houses
                ((Long) oCity.getStayCost1()).doubleValue(), // With 1 House
                ((Long) oCity.getStayCost2()).doubleValue(), // With 2 Houses
                ((Long) oCity.getStayCost3()).doubleValue()), // With 3 Houses
                // Set the City Buy Cost
                ((Long) oCity.getCost()).doubleValue(),
                // Set the House Cost for the City
                ((Long) oCity.getHouseCost()).doubleValue(),
                // Set the City State
                oState);
    }

    /**
     * Create the Surprise Card Deck
     * @param surprise The Surprise JAXB Configuration Deck
     * @return A Monopoly Surprise Cards Deck
     */
    public ArrayList<Card> getSurpriseCards(Surprise surprise) {
        ArrayList<Card> retSurprises = new ArrayList<Card>();
        List<CardBase> surpriseCards = surprise.getSurpriseCards();
        for (CardBase oCard : surpriseCards) {
            // Check witch Card we are handling
            switch (getCardType(oCard)) {
                case MONETARY_CARD: {
                    // Convert The Card Object to a Monetary Card
                    MonetaryCard monetaryCard = (MonetaryCard) oCard;
                    // Check from whom to pay
                    Boolean payToCashire = (monetaryCard.getWho().equalsIgnoreCase("TREASURY"));
                    // Duplicate the Number of Cards Acording to the Number in 'num'
                    for (int i = 0; i < oCard.getNum(); ++i) {
                        // Add the New Money Card to the Surprise Cards Array
                        retSurprises.add(new Money_card(CardType.SURPRISE_CARD, // Set as a Surprise Card
                                String.format(monetaryCard.getText(), monetaryCard.getSum()), // Set the Card Parsed Text
                                ((Long) monetaryCard.getSum()).doubleValue(), // Set the Amount to Pay
                                payToCashire)); // Set form whom to get Payed (Cashier / Players)
                    }
                    break;
                }
                case GOTO_CARD: {
                    // Convert the Card Object to a Go To Card
                    GotoCard gotoCard = (GotoCard) oCard;
                    // Duplicate the Number of Cards Acording to the Number in 'num'
                    for (int i = 0; i < oCard.getNum(); ++i) {
                        // Add the New Move Card to the Surprise Cards Array
                        retSurprises.add(new Move_card(CardType.SURPRISE_CARD, // Set as a Surprise Card
                                gotoCard.getText(), // Set the Card Description Text
                                parseGoTo(gotoCard.getTo()))); // Set to Where to Go
                    }
                    break;
                }
                case GETOUT_CARD: {
                    // Convert the card Object to a Get Out of Jail Card
                    GetOutOfJailCard jailCard = (GetOutOfJailCard) oCard;
                    retSurprises.add(new Card(CardType.SURPRISE_CARD, // Set as a Surprise Card
                            jailCard.getText())); // Set the Card Description Text
                    break;
                }
            }
        }
        // Return all the Surprise Cards
        return retSurprises;
    }

    /**
     * Create the Warning Card Deck
     * @param warrant The Warrant JAXB Configuration Deck
     * @return A Monopoly Warning Cards Deck
     */
    public ArrayList<Card> getWarningCards(Warrant warrant) {
        ArrayList<Card> retWarnings = new ArrayList<Card>();
        List<CardBase> warrantCards = warrant.getWarrantCards();
        for (CardBase oCard : warrantCards) {
            // Check witch Card we are handling
            switch (getCardType(oCard)) {
                case MONETARY_CARD: {
                    // Convert The Card Object to a Monetary Card
                    MonetaryCard monetaryCard = (MonetaryCard) oCard;
                    // Check from whom to pay
                    Boolean payToCashire = (monetaryCard.getWho().equalsIgnoreCase("TREASURY"));
                    // Duplicate the Number of Cards Acording to the Number in 'num'
                    for (int i = 0; i < oCard.getNum(); ++i) {
                        // Add the New Money Card to the Warning Cards Array
                        retWarnings.add(new Money_card(CardType.WARNING_CARD, // Set as a Warning Card
                                String.format(monetaryCard.getText(), monetaryCard.getSum()), // Set the Card Parsed Text
                                ((Long) (monetaryCard.getSum())).doubleValue(), // Set the Amount to Pay
                                payToCashire)); // Set to whom to Pay (Cashier / Players)
                    }
                    break;
                }
                case GOTO_CARD: {
                    // Convert the Card Object to a Go To Card
                    GotoCard gotoCard = (GotoCard) oCard;
                    // Duplicate the Number of Cards Acording to the Number in 'num'
                    for (int i = 0; i < oCard.getNum(); ++i) {
                        // Add the New Move Card to the Warning Cards Array
                        retWarnings.add(new Move_card(CardType.WARNING_CARD, // Set as a Warning Card
                                gotoCard.getText(), // Set the Card Description Text
                                parseGoTo(gotoCard.getTo()))); // Set to Where to Go
                    }
                    break;
                }
            }
        }
        // Return all the Warning Cards
        return retWarnings;
    }

    /**
     * Checks the Card Type
     * @param oCard The Card Object
     * @return The Card JAXB Monopoly Configuration Type
     */
    private JAXBCardTypes getCardType(CardBase oCard) {
        JAXBCardTypes ret = null;
        // Check if the Card is a Monetary Card
        if (oCard instanceof MonetaryCard) {
            ret = JAXBCardTypes.MONETARY_CARD;
        } // Check if the Card is a Goto Card
        else if (oCard instanceof GotoCard) {
            ret = JAXBCardTypes.GOTO_CARD;
        } // Check if the Card is a Get out of Prison Card
        else if (oCard instanceof GetOutOfJailCard) {
            ret = JAXBCardTypes.GETOUT_CARD;
        }
        return ret;
    }

    /**
     * Converts any known Place to go to in the Game to a Known Monopoly Enumerator Value
     * @param sGoto The Place to go to
     * @return A {@link eGoTo} Enumerator of the Places to Go To
     */
    private eGoTo parseGoTo(String sGoto) {
        // Check if the String contains any of the Known Places to Go to
        eGoTo ret = null;
        // Check if has the Word Start
        if (Pattern.compile(Pattern.quote("START"), Pattern.CASE_INSENSITIVE).matcher(sGoto).find()) {
            ret = eGoTo.START;
        } // Check if has the Word Surprise
        else if (Pattern.compile(Pattern.quote("SURPRISE"), Pattern.CASE_INSENSITIVE).matcher(sGoto).find()) {
            ret = eGoTo.NEXT_SURPRISE;
        } // Check if has the Word Warrant OR Warning
        else if (Pattern.compile(Pattern.quote("WARRANT"), Pattern.CASE_INSENSITIVE).matcher(sGoto).find()) {
            ret = eGoTo.NEXT_WARNING;
        } else if (Pattern.compile(Pattern.quote("WARNING"), Pattern.CASE_INSENSITIVE).matcher(sGoto).find()) {
            ret = eGoTo.NEXT_WARNING;
        } // Check if has the Word Jail OR Prison
        else if (Pattern.compile(Pattern.quote("JAIL"), Pattern.CASE_INSENSITIVE).matcher(sGoto).find()) {
            ret = eGoTo.PRISON;
        } else if (Pattern.compile(Pattern.quote("PRISON"), Pattern.CASE_INSENSITIVE).matcher(sGoto).find()) {
            ret = eGoTo.PRISON;
        }

        // Return the Requested Goto Enumerator
        return ret;
    }

    /**
     * Sets the Game Board Order From the JAXB Configuration Object
     * @param board The JAXB Configuration Object
     * @return An Array of the Squares by the Order from the Configuration File
     */
    public Square[] setBoardOrder(Board board) {
        // Create the Squares Array to Return
        Square[] retBoadSquares = new Square[36];
        // Get All the Cities in the Game
        City[] cities = getCitiesArray();
        // Get all the Transportations in the Game
        Transportation[] arrTransp = getTransportationsArray();
        // Get all the Services in the Game
        Service[] arrServ = getServicesArray();
        // Create an Index Counter for the Board
        Integer i = 0;
        // Create an Index Counter for the Assets (Cities, Transportations, Services)
        Integer c = 0, t = 0, s = 0;
        // Get the JAXB Board Content
        List<JAXBElement<? extends SquareBase>> jaxbBoard = board.getContent();
        try {
            // Loop over all the input Squares
            for (JAXBElement<? extends SquareBase> oSquare : jaxbBoard) {
                // Check if this is the Start Square
                if (oSquare.getValue() instanceof StartSquareType) {
                    fixedSquares.START_SQUARE.set(i);
                    retBoadSquares[i] = new Square("Start");
                } // Check if this is the Parking Square
                else if (oSquare.getValue() instanceof ParkingSquareType) {
                    fixedSquares.PARKING_SQUARE.set(i);
                    retBoadSquares[i] = new Square("Parking");
                } // Check if this is the Prison Square
                else if (oSquare.getValue() instanceof JailSlashFreeSpaceSquareType) {
                    fixedSquares.PRISON_SQUARE.set(i);
                    retBoadSquares[i] = new Square("Prison");
                } // Check if this is the Go to Prison Square
                else if (oSquare.getValue() instanceof GotoJailSquareType) {
                    fixedSquares.GO2PRISON_SQUARE.set(i);
                    retBoadSquares[i] = new Square("Go to Prison");
                } // This must be a normal Square
                else {
                    // Convert the Square Object to a Square Type
                    SquareType squareType = (SquareType) oSquare.getValue();
                    // Check if its a City
                    if (squareType.getType().equals("CITY")) {
                        retBoadSquares[i] = cities[c];
                        // Increment the Cities Index Counter
                        c++;
                    } // Check if its a Transportation
                    else if (squareType.getType().equals("TRANSPORTATION")) {
                        retBoadSquares[i] = arrTransp[t];
                        // Increment the Transportations Counter
                        t++;
                    } // Check if its a Utility
                    else if (squareType.getType().equals("UTILITY")) {
                        retBoadSquares[i] = arrServ[s];
                        // Increment the Services Index Counter
                        s++;
                    } // Check if its a Surprise Square
                    else if (squareType.getType().equals("SURPRISE")) {
                        retBoadSquares[i] = new Square("Surprise");
                    } // Check if its a Warrant Square
                    else if (squareType.getType().equals("WARRANT")) {
                        retBoadSquares[i] = new Square("Warning");
                    } // Else - Unknown Type
                    else {
                        System.out.println(String.format("Error!\nSquare Type Unknown (Type: %s)", squareType.getType()));
                    }
                }
                // Increment the Board Index Counter
                i++;
            } // End of For Loop
        } catch (Exception e) {
            // This should not Get Here
            e.printStackTrace();
        }
        // A Normal Monopoly Board has 36 Squares
        // Check that the Number of Squares from the XML file is 36
        if (i < 35) {
            // Inform the User
            System.out.println("Number of Squares in the File is to small.\nOnly " + i + " squares in the File.");
            // Exit with Error
            System.exit(1);
        }
        // Return the New Board Order
        return retBoadSquares;
    }

    /**
     * @return An Array of All the Services
     */
    private Service[] getServicesArray() {
        Service[] arrServices = new Service[0];
        return allServices.getAssets().toArray(arrServices);
    }

    /**
     * @return An Array of All the Transportations
     */
    private Transportation[] getTransportationsArray() {
        Transportation[] arrTransport = new Transportation[0];
        return allTransportaions.getAssets().toArray(arrTransport);
    }

    /**
     * Creates a Unified Array for all the Cities in the Game
     * @return  The Cities Array
     */
    private City[] getCitiesArray() {
        ArrayList<City> retCities = new ArrayList<City>();
        City[] arrCities = new City[0];
        try {
            // Loop over All the States
            for (State oState : states) {
                for (City oCity : oState.getCities()) {

                    retCities.add(oCity);
                }
            }
        } catch (Exception e) {
            // This should not Get Here
            e.printStackTrace();
        }
        // Return the Cities Array
        return retCities.toArray(arrCities);
    }

    /**
     * @return The Parsing Status
     */
    public boolean getParseOK() {
        return parseOK;
    }
}
